#include "PrimitiveRendererBase.h"
#include "RendererBase.h"
#include "..\Schematics\Block.h"
#include "..\Schematics\Schematic.h"

PrimitiveRendererBase::PrimitiveRendererBase(RendererBase *r) :
	_r(r),
	CacheProvider(nullptr),
	CullProvider(nullptr),
	PositionProvider(nullptr),
	LightProvider(nullptr),
	ProgressReporter(nullptr),
	_currentList(nullptr),
	_m()
{
};

/// <summary>
/// Translate the view matrix.
/// </summary>
/// <param name="x">X value.</param>
/// <param name="y">Y value.</param>
/// <param name="z">Z value.</param>
void PrimitiveRendererBase::Translate(const float &x, const float &y, const float &z)
{
	_m.translate(Point3f(x, y, z));
}

/// <summary>
/// Rotate the view matrix.
/// </summary>
/// <param name="angle">Angle in degrees.</param>
void PrimitiveRendererBase::Rotate(const float &angle, const int &axisX, const int &axisY, const int &axisZ)
{
	_m.rotate(MathHelper::DegreesToRadians(angle), Point3i(axisX, axisY, axisZ));
}

/// <summary>
/// Scale the view matrix.
/// </summary>
/// <param name="x">X scale.</param>
/// <param name="y">Y scale.</param>
/// <param name="z">Z scale.</param>
void PrimitiveRendererBase::Scale(const float &x, const float &y, const float &z)
{
	_m.scale(Point3f(x, y, z));
}

/// <summary>
/// Save the current matrix state.
/// </summary>
void PrimitiveRendererBase::PushState()
{
	_states.push(_m);
}

/// <summary>
/// Restore matching pushed matrix state.
/// </summary>
void PrimitiveRendererBase::PopState()
{
	_m = _states.top();
	_states.pop();
}

RenderListBase *PrimitiveRendererBase::CreateRenderList()
{
	return new RenderListBase();
}

void PrimitiveRendererBase::SetupList(ITexture *texture)
{
	if (_currentList != nullptr && _currentList->Texture != texture)
		_currentList = nullptr;

	if (_currentList == nullptr)
	{
		for (auto it = _renderLists.begin(); it != _renderLists.end(); ++it)
		{
			if ((*it)->Texture == texture)
			{
				_currentList = (*it);
				break;
			}
		}
			
		if (_currentList == nullptr)
		{
			_currentList = CreateRenderList();
			_currentList->Texture = texture;
			_renderLists.push_back(_currentList);
		}
	}
}

void PrimitiveRendererBase::Draw(const Face &face, const bool &reverse)
{
	if (face.Color.A == 0)
		return;

	SetupList(face.Texture);

	Face multiplied = Face(face).Multiply(_m);

	if (PositionProvider)
		multiplied.Block = PositionProvider->GetPosition();

	_currentList->Faces.push_back(multiplied);
			
	if (reverse)
		_currentList->Faces.push_back(multiplied.Reverse());
}

/// <summary>
/// Draw this box to this chunk renderer.
/// </summary>
/// <param name="box">Box to draw.</param>
void PrimitiveRendererBase::Draw(const Box &box)
{
	SetupList(box.Texture);

	PushState();
	Translate(box.Position.X, box.Position.Y, box.Position.Z);

	RenderBox rb;

	rb.Full = rb.Opaque = true;

	if (PositionProvider)
		rb.Position = PositionProvider->GetPosition();

	// South
	rb.Faces[0] = Face(
				Point3f(box.Bounds.X, 0, box.Inset[0]),
				Point3f(0, 0, box.Inset[0]),
				Point3f(0, box.Bounds.Y, box.Inset[0]),
				Point3f(box.Bounds.X, box.Bounds.Y, box.Inset[0]),
				box.Colors[0], box.Texture, box.TexCoords[0], box.Bounds.X, box.Bounds.Y);

	// East
	rb.Faces[1] = Face(
				Point3f(box.Bounds.X - box.Inset[1], 0, box.Bounds.Z),
				Point3f(box.Bounds.X - box.Inset[1], 0, 0),
				Point3f(box.Bounds.X - box.Inset[1], box.Bounds.Y, 0),
				Point3f(box.Bounds.X - box.Inset[1], box.Bounds.Y, box.Bounds.Z),
				box.Colors[1], box.Texture, box.TexCoords[1], box.Bounds.Z, box.Bounds.Y);

	// North
	rb.Faces[2] = Face(
				Point3f(0, 0, box.Bounds.Z - box.Inset[2]),
				Point3f(box.Bounds.X, 0, box.Bounds.Z - box.Inset[2]),
				Point3f(box.Bounds.X, box.Bounds.Y, box.Bounds.Z - box.Inset[2]),
				Point3f(0, box.Bounds.Y, box.Bounds.Z - box.Inset[2]),
				box.Colors[2], box.Texture, box.TexCoords[2], box.Bounds.X, box.Bounds.Y);

	// West
	rb.Faces[3] = Face(
				Point3f(box.Inset[3], 0, 0),
				Point3f(box.Inset[3], 0, box.Bounds.Z),
				Point3f(box.Inset[3], box.Bounds.Y, box.Bounds.Z),
				Point3f(box.Inset[3], box.Bounds.Y, 0),
				box.Colors[3], box.Texture, box.TexCoords[3], box.Bounds.Z, box.Bounds.Y);

	// Bottom
	rb.Faces[4] = Face(
				Point3f(box.Bounds.X - box.Inset[0], box.Bounds.Y - box.Inset[4], box.Inset[1]),
				Point3f(box.Inset[2], box.Bounds.Y - box.Inset[4], box.Inset[1]),
				Point3f(box.Inset[2], box.Bounds.Y - box.Inset[4], box.Bounds.Z - box.Inset[3]),
				Point3f(box.Bounds.X - box.Inset[0], box.Bounds.Y - box.Inset[4], box.Bounds.Z - box.Inset[3]),
				box.Colors[4], box.Texture, box.TexCoords[4] + Point2i(box.Inset[0], box.Inset[1]), box.Bounds.X - (box.Inset[0] + box.Inset[2]), box.Bounds.Z - (box.Inset[1] + box.Inset[3]));

	// Top
	rb.Faces[5] = Face(
				Point3f(box.Inset[0], box.Inset[5], box.Inset[1]),
				Point3f(box.Bounds.X - box.Inset[2], box.Inset[5], box.Inset[1]),
				Point3f(box.Bounds.X - box.Inset[2], box.Inset[5], box.Bounds.Z - box.Inset[3]),
				Point3f(box.Inset[0], box.Inset[5], box.Bounds.Z - box.Inset[3]),
				box.Colors[5], box.Texture, box.TexCoords[5] + Point2i(box.Inset[0], box.Inset[1]), box.Bounds.X - (box.Inset[0] + box.Inset[2]), box.Bounds.Z - (box.Inset[1] + box.Inset[3]));

	for (int i = 0; i < 6; ++i)
		rb.Faces[i].Multiply(_m);

	_currentList->Boxes.push_back(rb);

	PopState();
}

static int _totalFaces = 0, _finalFaces = 0;

/// <summary>
/// Called when the chunk is done rendering.
/// </summary>
void PrimitiveRendererBase::Compile(const bool &allowCacheLoading)
{
	int ri = 0;
	
	if (ProgressReporter != nullptr)
	{
		int max = 0;

		for (auto it = _renderLists.begin(); it != _renderLists.end(); ++it)
		{
			auto list = (*it);
			max += list->Faces.size() + (list->Boxes.size() * 6);
		}

		ProgressReporter->SetProgressGoal(max, "Compiling");
	}

	for (auto it = _renderLists.begin(); it != _renderLists.end(); ++it, ++ri)
	{
		auto list = (*it);

		if (allowCacheLoading && CacheProvider != nullptr && list->Loaded)
			CacheProvider->Load(ri, list);

		list->CompiledVertices.clear();
		list->CompiledVertices.reserve(list->Faces.size() * 4 + list->Boxes.size() * 6 * 4);

		_totalFaces += list->Faces.size() + list->Boxes.size() * 6;

		for (auto fit = list->Faces.begin(); fit != list->Faces.end(); ++fit)
		{
			if (ProgressReporter != nullptr)
				if (ProgressReporter->IncrementProgress())
					return;

			Face &face = (*fit);
			
			if (CullProvider &&
				CullProvider->CullObject(face.Block))
				continue;

			_finalFaces++;

			for (byte i = 0; i < 4; ++i)
			{
				RenderVertex v;
				memcpy(&v.Point.X, &face.Points[i].X, sizeof(Point3f));
				memcpy(&v.TexCoord.X, &face.TexCoords[i].X, sizeof(Point2f));

				v.Color[0] = face.Color.R / 255.0f;
				v.Color[1] = face.Color.G / 255.0f;
				v.Color[2] = face.Color.B / 255.0f;
				v.Color[3] = face.Color.A / 255.0f;

				list->CompiledVertices.push_back(v);
			}
		}

		for (auto fit = list->Boxes.begin(); fit != list->Boxes.end(); ++fit)
		{
			RenderBox &rb = (*fit);
				
			if (CullProvider &&
				CullProvider->CullObject(rb.Position))
				continue;

			for (byte f = 0; f < 6; ++f)
			{
				if (ProgressReporter != nullptr)
					if (ProgressReporter->IncrementProgress())
						return;

				if (CullProvider &&
					CullProvider->CullBlockSide(rb, f))
					continue;

				auto &face = rb.Faces[f];
				_finalFaces++;

				Colorb faceColors[4];

				for (byte i = 0; i < 4; ++i)
					memcpy(&faceColors[i].R, &face.Color.R, sizeof(Colorb));

				if (LightProvider)
					LightProvider->LightBoxSide(rb, f, face, faceColors);

				for (byte i = 0; i < 4; ++i)
				{
					RenderVertex v;
					
					memcpy(&v.Point.X, &face.Points[i].X, sizeof(Point3f));
					memcpy(&v.TexCoord.X, &face.TexCoords[i].X, sizeof(Point2f));

					v.Color[0] = faceColors[i].R / 255.0f;
					v.Color[1] = faceColors[i].G / 255.0f;
					v.Color[2] = faceColors[i].B / 255.0f;
					v.Color[3] = faceColors[i].A / 255.0f;

					list->CompiledVertices.push_back(v);
				}
			}
		}

		list->CompiledVertices.shrink_to_fit();
		list->CompiledSize = list->CompiledVertices.size();

		if (CacheProvider != nullptr && !list->Loaded)
			CacheProvider->Save(ri, list);
			
		list->Unload();
		list->Loaded = true;
	}
}

void PrimitiveRendererBase::ExecuteRenderer(Schematic *schematic, const Block &block, const Point3i &position)
{
	_r->ExecuteRenderer(this, schematic, block, position);
}

bool PrimitiveRendererBase::IsFullBlockAt(const int &x, const int &y, const int &z)
{
	return _r->IsFullBlockAt(x, y, z);
}

bool PrimitiveRendererBase::IsOpaqueBlockAt(const int &x, const int &y, const int &z)
{
	return _r->IsOpaqueBlockAt(x, y, z);
}